From 0a48413eddaf53884abd798b4f20b8de3ff9eb87 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 6 Jan 2026 11:06:40 -0600 Subject: [PATCH] [PATCH 2/4] dnp3: reduce flood threshold to 32 and make configurable Lower the number of unreplied requests from 500 to 32 to consider a flood. At the very least this is an anomaly given the DNP3 spec mentions that DNP3 should only have one outstanding request at a time, with an exception for unsolicited responses, so in practice no more than 2 should be seen. Additionally make this value configurable by introducing the max-tx parameter. Ticket: #8181 (cherry picked from commit a16f087b93be1ff2f2edf47371866ad9b28593c1) Origin: upstream, https://github.com/OISF/suricata/commit/635af8dc8be09667689be71d781912718ca1aa49.patch Bug: https://redmine.openinfosecfoundation.org/issues/8181 Subject: Upstream fix for CVE-2026-22259 part 2 Gbp-Pq: Name CVE-2026-22259_2.patch --- doc/userguide/upgrade.rst | 10 ++++++++++ src/app-layer-dnp3.c | 29 +++++++++++++++++++---------- suricata.yaml.in | 1 + 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 26df95dd..ed4e59c7 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -34,6 +34,16 @@ also check all the new features that have been added but are not covered by this guide. Those features are either not enabled by default or require dedicated new configuration. +Upgrading to 7.0.14 (trixie-security 1:7.0.10-1~bpo13u3) +------------------- + +Other Changes +~~~~~~~~~~~~~ +- ``dnp3`` has reduced the default maximum number of outstanding + transactions from 500 down to 32. A ``max-tx`` parameter has been + added to the ``dnp3`` parser for users that need a larger number of + in-flight transactions. + Upgrading to 7.0.9 ------------------ - The AF_PACKET default block size for both TPACKET_V2 and TPACKET_V3 diff --git a/src/app-layer-dnp3.c b/src/app-layer-dnp3.c index 4f210f03..15de9bbe 100644 --- a/src/app-layer-dnp3.c +++ b/src/app-layer-dnp3.c @@ -40,9 +40,6 @@ #include "app-layer-dnp3.h" #include "app-layer-dnp3-objects.h" -/* Default number of unreplied requests to be considered a flood. */ -#define DNP3_DEFAULT_REQ_FLOOD_COUNT 500 - #define DNP3_DEFAULT_PORT "20000" /* Expected values for the start bytes. */ @@ -93,6 +90,14 @@ enum { /* Extract the range code from the object qualifier. */ #define DNP3_OBJ_RANGE(x) (x & 0xf) +/* Default number of unreplied requests to be considered a flood. + * + * DNP3 is a request/response SCADA protocol with typically only 1-2 + * transactions in flight. But set a limit high enough to allow for + * some pipelining but reduce the chance of memory exhaustion + * attacks. */ +static uint64_t dnp3_max_tx = 32; + /* Decoder event map. */ SCEnumCharMap dnp3_decoder_event_table[] = { {"FLOODED", DNP3_DECODER_EVENT_FLOODED}, @@ -514,7 +519,7 @@ static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3, bool request) TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next); /* Check for flood state. */ - if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) { + if (dnp3->unreplied > dnp3_max_tx && !dnp3->flooded) { DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED); dnp3->flooded = 1; } @@ -1384,7 +1389,7 @@ static void DNP3StateTxFree(void *state, uint64_t tx_id) dnp3->unreplied--; /* Check flood state. */ - if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) { + if (dnp3->flooded && dnp3->unreplied < dnp3_max_tx) { dnp3->flooded = 0; } @@ -1430,8 +1435,7 @@ static int DNP3GetAlstateProgress(void *tx, uint8_t direction) int retval = 0; /* If flooded, "ack" old transactions. */ - if (dnp3->flooded && (dnp3->transaction_max - - dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) { + if (dnp3->flooded && (dnp3->transaction_max - dnp3tx->tx_num >= dnp3_max_tx)) { SCLogDebug("flooded: returning tx as done."); SCReturnInt(1); } @@ -1604,8 +1608,13 @@ void RegisterDNP3Parsers(void) AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetTxData); AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, DNP3GetStateData); - } - else { + + /* Parse max-tx configuration. */ + intmax_t value = 0; + if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) { + dnp3_max_tx = (uint64_t)value; + } + } else { SCLogConfig("Parser disabled for protocol %s. " "Protocol detection still on.", proto_name); } @@ -2252,7 +2261,7 @@ static int DNP3ParserTestFlooded(void) FAIL_IF_NOT(tx->done); FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER)); - for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) { + for (uint64_t i = 0; i < dnp3_max_tx - 1; i++) { SCMutexLock(&flow.m); FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3, STREAM_TOSERVER, request, sizeof(request))); diff --git a/suricata.yaml.in b/suricata.yaml.in index b95d5e62..1b54e1f2 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1161,6 +1161,7 @@ app-layer: enabled: no detection-ports: dp: 20000 + #max-tx: 32 # SCADA EtherNet/IP and CIP protocol support enip: -- 2.30.2